/* -*- c++ -*- */

#ifndef _OBJECT_H_
#define _OBJECT_H_

#include "config.h"
#include "Punto.h"
#include "Box.h"
#include "Scalar.h"
#include "bit_array.h"

class OglArray;
class Linea;
class Triangulo;

typedef enum {
  OBJ_DRAW_EDGES = 0,
  OBJ_DRAW_POLYGONS,
  OBJ_DRAW_EDGES_POLYGONS
} _t_obj_draw_mode;

typedef enum {
  OBJ_DRAW_LINES = 0,
  OBJ_DRAW_TRIANGLES,
  OBJ_DRAW_BOTH
} _t_obj_draw_elements;

typedef enum {
  OBJ_CULL_NONE = 0,
  OBJ_CULL_FRONT,
  OBJ_CULL_BACK
} _t_obj_cull_mode;

inline const char *str_obj_draw_mode( const _t_obj_draw_mode mode) {
  const char *ret = "N/A";
  switch( mode) {
    case OBJ_DRAW_EDGES:          ret = "draw edges"; break;
    case OBJ_DRAW_POLYGONS:       ret = "draw body"; break;
    case OBJ_DRAW_EDGES_POLYGONS: ret = "draw body & edges"; break;
  }
  return ret;
}

inline const char *str_obj_draw_elements( const _t_obj_draw_elements which) {
  const char *ret = "N/A";
  switch( which) {
    case OBJ_DRAW_LINES:     ret = "draw lines"; break;
    case OBJ_DRAW_TRIANGLES: ret = "draw triangles"; break;
    case OBJ_DRAW_BOTH :     ret = "draw lines & triangles"; break;
  }
  return ret;
}

inline const char *str_obj_cull_mode( const _t_obj_cull_mode mode) {
  const char *ret = "N/A";
  switch( mode) {
    case OBJ_CULL_NONE:  ret = "no cull"; break;
    case OBJ_CULL_FRONT: ret = "cull front faces"; break;
    case OBJ_CULL_BACK:  ret = "cull back faces"; break;
  }
  return ret;
}

class Object {
 public:
  Object(): _name( NULL), _num_puntos( 0), _num_lineas( 0), _num_triangulos( 0),
            _lst_puntos( NULL), _lst_lineas( NULL), _lst_triangulos( NULL),
            _lst_normales( NULL), _lst_scalars( NULL),
            _ogl_array( NULL), _render_method( OGLARRAY_BUFFER_OBJECT),
            _current_view_mode( OBJ_DRAW_POLYGONS), _current_cull_mode( GL_NONE),
            _draw_which_elements( OGLARRAY_LINES | OGLARRAY_TRIANGLES | OGLARRAY_QUADS),
            _draw_bbox( true), _dirty( false), _accum_rot_y( 0.0f) {
  };
  Object( int num_points, int num_lines, int num_triangles, int num_quads,
          const float *lst_points,
          const int *lst_lines, const int *lst_triangles, const int *lst_quads,
          const float *lst_normals, const float *lst_scalar)
      : _name( NULL), _num_puntos( 0), _num_lineas( 0), _num_triangulos( 0),
        _lst_puntos( NULL), _lst_lineas( NULL), _lst_triangulos( NULL),
        _lst_normales( NULL), _lst_scalars( NULL),
        _ogl_array( NULL), _render_method( OGLARRAY_BUFFER_OBJECT),
        _current_view_mode( OBJ_DRAW_POLYGONS), _current_cull_mode( GL_NONE),
        _draw_which_elements( OGLARRAY_LINES | OGLARRAY_TRIANGLES | OGLARRAY_QUADS),
        _draw_bbox( true), _dirty( false), _accum_rot_y( 0.0f) {
    SetData( num_points, num_lines, num_triangles, num_quads,
             lst_points, lst_lines, lst_triangles, lst_quads, lst_normals, lst_scalar);
  }
  Object( int num_points, int num_lines, int num_triangles,
          const Punto *lst_points, const Linea *lst_lines, const Triangulo *lst_triangles)
      : _name( NULL), _num_puntos( 0), _num_lineas( 0), _num_triangulos( 0),
        _lst_puntos( NULL), _lst_lineas( NULL), _lst_triangulos( NULL),
        _lst_normales( NULL), _lst_scalars( NULL),
        _ogl_array( NULL), _render_method( OGLARRAY_BUFFER_OBJECT),
        _current_view_mode( OBJ_DRAW_POLYGONS), _current_cull_mode( GL_NONE),
        _draw_which_elements( OGLARRAY_LINES | OGLARRAY_TRIANGLES | OGLARRAY_QUADS),
        _draw_bbox( true), _dirty( false), _accum_rot_y( 0.0f) {
    SetData( num_points, num_lines, num_triangles, lst_points, lst_lines, lst_triangles);
  }
  ~Object() {
    reset();
  }
  
  void reset();

  void set_name( const char *str) {
    if ( _name) free( _name);
    _name = NULL;
    if ( str && *str)
      _name = strdup( str);
  }
  const char *get_name() const { return _name;}

  void set_draw_bbox( bool b) { _draw_bbox = b;}
  bool get_draw_bbox() const { return _draw_bbox;}
  
  void SetData( int num_points, int num_lines, int num_triangles, int num_quads,
                const float *lst_points,
                const int *lst_lines,
                const int *lst_triangles,
                const int *lst_quads,
                const float *lst_normals,
                const float *lst_scalar);

  void SetData( int num_points, int num_lines, int num_triangles, 
                const Punto *lst_points, const Linea *lst_lines, const Triangulo *lst_triangles);

  int get_num_puntos() const { return _num_puntos;}
  Punto *get_lst_puntos() const { return _lst_puntos;}
  const Scalars **get_lst_scalars() const { return ( const Scalars **)_lst_scalars;}
  int get_num_lineas() const { return _num_lineas;}
  Linea *get_lst_lineas() const { return _lst_lineas;}
  int get_num_triangulos() const { return _num_triangulos;}
  Triangulo *get_lst_triangulos() const { return _lst_triangulos;}

  _t_ogl_render_method GetRenderMethod() const { return _render_method;}
  void SetRenderMethod( const _t_ogl_render_method rm);
  
  void SetViewStyle( const _t_obj_draw_mode mode) { _current_view_mode = mode;}
  void SetViewElements( const _t_obj_draw_elements which);
  void SetCullMode( const _t_obj_cull_mode mode);
  void SwapNormals();
  void SwapTrianglesSense();

  float diagonal() const;
  Punto centro() const;
  Box get_bbox() const { return _caja_contenedora;}

  void draw(); // calls CreateOpenGLBO()

  int WriteGidFiles( const char *filename) const;
  int WritePlyFile( const char *filename) const;

  void scale_to( float max_dim);
  void set_centre( const Punto &new_cent);
  void set_centre( float x, float y, float z) { set_centre( Punto( x, y, z));}
  void fit_into( const Box &space);
  void rotate_y( float degrees);
  void reset_rotation() { if ( _accum_rot_y != 0.0f) rotate_y( -_accum_rot_y);}
  
 private:
  void set_dirty( bool val) { _dirty = val;}
  void ResetOpenGLBO();
  int CreateOpenGLBO();
  int RenderOpenGLBO( _t_obj_draw_mode draw_mode) const;
  // void drawTeapot( _t_obj_draw_mode draw_mode) const;
  // void drawSphere( _t_obj_draw_mode draw_mode) const;
  void ActualizeBBox();
  void cull_on();
  void cull_off();
  
 private:
  char *_name;
  int _num_puntos, _num_lineas, _num_triangulos;
  Punto *_lst_puntos;
  Linea *_lst_lineas;
  Triangulo *_lst_triangulos;
  Vector *_lst_normales;
  Scalars **_lst_scalars;
  Box _caja_contenedora;
  OglArray *_ogl_array;
  _t_ogl_render_method _render_method;
  _t_obj_draw_mode _current_view_mode;
  GLenum _current_cull_mode;
  int _draw_which_elements;
  bool _draw_bbox, _dirty;
  float _accum_rot_y;
};

inline void Object::SetViewElements( const _t_obj_draw_elements which) {
  switch( which) {
    case OBJ_DRAW_LINES:
      _draw_which_elements = OGLARRAY_LINES;
      break;
    case OBJ_DRAW_TRIANGLES:
      _draw_which_elements = OGLARRAY_TRIANGLES | OGLARRAY_QUADS;
      break;
    case OBJ_DRAW_BOTH:
      _draw_which_elements = OGLARRAY_LINES | OGLARRAY_TRIANGLES | OGLARRAY_QUADS;
      break;
  }
}

inline void Object::SetCullMode( const _t_obj_cull_mode mode) {
  switch( mode) {
    case OBJ_CULL_NONE:
      _current_cull_mode = GL_NONE;
      break;
    case OBJ_CULL_FRONT:
      _current_cull_mode = GL_FRONT;
      break;
    case OBJ_CULL_BACK:
      _current_cull_mode = GL_BACK;
      break;
  }
}

inline void Object::cull_on() {
  if ( _current_cull_mode != GL_NONE) {
    glEnable( GL_CULL_FACE);
    glCullFace( _current_cull_mode);
  }
}

inline void Object::cull_off() {
  if ( _current_cull_mode != GL_NONE) {
    glDisable( GL_CULL_FACE);
  }
}

#endif // _OBJECT_H_
